package com.woniuxy.system.service.impl;
import java.math.BigDecimal;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;

import cn.hutool.core.lang.hash.Hash;
import cn.hutool.crypto.digest.BCrypt;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.woniuxy.common.exception.Assert;
import com.woniuxy.common.model.dto.bank.BankCardAddDTO;
import com.woniuxy.common.model.dto.bank.CardBindDto;
import com.woniuxy.common.model.dto.bank.SignatureDto;
import com.woniuxy.common.model.dto.user.LoginDto;
import com.woniuxy.common.model.dto.user.UserRegisterDto;
import com.woniuxy.common.model.vo.user.LoginVo;
import com.woniuxy.common.model.vo.user.UserInfoVo;
import com.woniuxy.common.result.enums.impl.BusinessCode;
import com.woniuxy.common.utils.JwtTemplate;
import com.woniuxy.common.utils.TokenUtils;
import com.woniuxy.sddfp.api.bank.TradeLogApi;
import com.woniuxy.system.entity.User;
import com.woniuxy.system.entity.UserCard;
import com.woniuxy.system.entity.UserDetail;
import com.woniuxy.system.mapper.UserMapper;
import com.woniuxy.system.service.IUserCardService;
import com.woniuxy.system.service.IUserDetailService;
import com.woniuxy.system.service.IUserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import javax.naming.ldap.HasControls;

/**
 * <p>
 * 用户表 服务实现类
 * </p>
 *
 * @author yellow docker
 * @since 2025-03-18
 *
 *   save 和 insert 区别?  往数据库添加数据
 *     service   业务           save (自己写save 可以操作多个insert)
 *     mapper    连接数据库     insert
 *
 *   命名规范问题:
 *     查询
 *          get    单个查询
 *          list  查询的事集合
 *          page  分页查询
 *     添加
 *          save           单个添加
 *          saveBatch      批量添加 (集合批处理)
 *          saveOrUpdate   如果数据存在就是更新 如果不存在就是添加
 *     修改
 *          update
 *     删除
 *         remove       删除
 *         removeByXXX  通过条件删除
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

    @Autowired
    private IUserDetailService userDetailService;

    // StringRedisTemplate 和 RedisTemplate 有区别吗?
    // StringRedisTemplate(我只吃饭) 和 RedisTemplate(我都吃) 有区别吗?

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private JwtTemplate jwtTemplate;

    @Autowired
    private IUserDetailService userDetailServiceImpl;

    @Autowired
    private IUserCardService userCardService;

    /**
     * 注册功能实现
     * @param userRegisterDto
     */
    @Transactional(
            propagation = Propagation.REQUIRED, //事务传播行为  无脑选择REQUIRED (有事务我就添加 没有事务我就 创建一个事务)
            rollbackFor = Exception.class  //事务回滚的异常 spring默认是 RuntimeException 如果抛出的异常超过这个 事务失效
    )
    @Override
    public void register(UserRegisterDto userRegisterDto) {
        /**
         *  业务逻辑:
         *    1、判断手机号码是否存在    如果存在抛出异常
         *    2、判断用户名是否存在      如果存在抛出异常
         *    3、添加 数据 (数据库表结构)
         *                user 表中  添加字段  userName pwd、phone
         *                user_detail 中 添加字段  phone
         *      业务中一定要注意 往那个表中添加、修改那个字段 返回结果
         */

        /**
         * mp 中有查询对象
         *       QueryWrapper  查询条件对象
         *       LambdaQueryWrapper  （查询对象 支持Lanbda语法）
         */
//        QueryWrapper<User> queryWrapper = new QueryWrapper();
//        queryWrapper.eq("phone", userRegisterDto.getPhone());
//        queryWrapper.eq("username", userRegisterDto.getUsername());
//        User phoneUser = getOne(queryWrapper);
//
        /**
         * Wrappers.lambdaQuery(User.class)  构造  LambdaQueryWrapper 对象
         *   User::getPhone, userRegisterDto.getPhone() 对查询条件赋值
         */
        LambdaQueryWrapper<User> userLambdaQueryWrapper =
                Wrappers.lambdaQuery(User.class)
                        .eq(User::getPhone, userRegisterDto.getPhone());

        //getOne 是父类 提供的查询方法
        User phoneUser = getOne(userLambdaQueryWrapper);

        //用户以存在 抛出异常
        Assert.error(phoneUser != null, BusinessCode.USER_EXISTED);

        User usernameUser = getOne(Wrappers.lambdaQuery(User.class).eq(User::getUserName, userRegisterDto.getUserName()));
        //用户以存在 抛出异常
        Assert.error(usernameUser != null, BusinessCode.USER_EXISTED);

        saveUser(userRegisterDto);

        //插件 setALL
        // ALT + Entry

    }

    private void saveUser(UserRegisterDto userRegisterDto) {
        //保存数据
        User user = new User();
        user.setPhone(userRegisterDto.getPhone());
        user.setUserName(userRegisterDto.getUserName());
        //用hutool 工具进行加密
//        String pwd = BCrypt.hashpw(userRegisterDto.getPwd());
        user.setPwd(userRegisterDto.getPwd());
        //密码校验
//        BCrypt.checkpw() 密码校验
//        user.setRealName("");  为null 后期 其他的绑卡业务中更新
//        user.setIdentityCode(""); 为null 后期 其他的绑卡业务中更新
        user.setUserType("1");
        user.setUserStatus(true);

        save(user);

        UserDetail userDetail = new UserDetail();
        // mp 添加user的时候 会自动把 添加数据库中的 id 返回给当前的user 对象

        userDetail.setId(user.getId());
        userDetail.setUserName(userRegisterDto.getUserName());
        userDetail.setPhone(userRegisterDto.getPhone());
        userDetail.setBorrowMoney(new BigDecimal("0"));
        //随机生成  规则 UC 开头的字符串就可以
        userDetail.setUserCode("UC_" + UUID.randomUUID().toString().substring(0,12));
        userDetail.setCardBindStatus(false);

        userDetailService.save(userDetail);
    }

    @Override
    public LoginVo login(LoginDto loginDto) {
        return loginDto.getIsPhoneLogin() ? loginByPhone(loginDto) : loginByAccount(loginDto);
    }

    /**
     * 绑卡 业务实现 本质就是操作 表中的字段
     *      * 用户绑卡：
     *      * 1、从token中获取userId
     *      * 2、校验：根据用户id查询u_user_detail用户表，根据CARD_BIND_STATUS字段判断是否已经绑卡
     *      * 3、如果未绑卡，保存保卡信息到u_user_card表
     *      * 4、更新用户表：把请求参数中的用户真实姓名、身份证号码更新到u_user表中 (用户名 id )
     *      * 5、生成请求流水号，用来访问托管代理， 流水号
     *          生成请求流水号，用来访问托管代理，并更新u_user_detail表中流水号字段REQ_CODE
     *      * 6、远程访问托管代理服务（暂不实现）
     *
     * @param cardBindDto
     * @return
     */

    @Autowired
    private TradeLogApi tradeLogApi;

    @Override
    public SignatureDto addBankAccount(CardBindDto cardBindDto) {

        //01 获取token中的userId
        String userId = TokenUtils.getUserIdByToken("userId");

        //02 校验：根据用户id查询u_user_detail用户表，根据CARD_BIND_STATUS字段判断是否已经绑卡
        LambdaQueryWrapper<UserDetail> userDetailLambdaQueryWrapper = Wrappers.lambdaQuery(UserDetail.class).eq(UserDetail::getId, userId);
        UserDetail userDetail = userDetailServiceImpl.getOne(userDetailLambdaQueryWrapper);


//        boolean cardBindStatus1 = userDetail.isCardBindStatus();  //false
//        Boolean cardBindStatus2 = userDetail.getCardBindStatus(); //false

        Boolean cardBindStatus1 = true;  //不抛出异常
        boolean cardBindStatus2 = true;

        //判断  false
        Assert.error(userDetail.isCardBindStatus(),BusinessCode.USER_CARD_ALREADY_BINDED);
        //3、如果未绑卡，保存保卡信息到u_user_card表 (mysql 添加表 + 代码生成器 )
        UserCard userCard = new UserCard();
        //userId
        userCard.setUserId(Long.valueOf(userId));
//        userCard.setBankNo("");
//        userCard.setBankTitle("");  等待银行给我们返回数据 然后更新
        userCard.setCardNo(cardBindDto.getCardNo());
        userCard.setPhone(userDetail.getPhone());
        userCard.setStatus(false);
        userCardService.save(userCard);

        //4、更新用户表：把请求参数中的用户真实姓名、身份证号码更新到u_user表中 (用户名 id )
        LambdaUpdateWrapper<User> userLambdaUpdateWrapper = Wrappers.lambdaUpdate(User.class).eq(User::getId, userId)
                .set(User::getRealName, cardBindDto.getRealName())
                .set(User::getIdentityCode, cardBindDto.getIdentityCode());
        update(userLambdaUpdateWrapper);

        //5、生成请求流水号，用来访问托管代理，并更新u_user_detail表中流水号字段REQ_CODE
        String reqCode = UUID.randomUUID().toString().replace("-","").substring(0, 10);
        LambdaUpdateWrapper<UserDetail> userDetailLambdaUpdateWrapper = Wrappers.lambdaUpdate(UserDetail.class).eq(UserDetail::getId, userId)
                .set(UserDetail::getReqCode, reqCode);
        userDetailService.update(userDetailLambdaUpdateWrapper);

        // 绑卡添加对象
        BankCardAddDTO bankCardAddDTO = new BankCardAddDTO();
        bankCardAddDTO.setReqCode(reqCode);
        bankCardAddDTO.setUserId(Long.valueOf(userId));

        //远程调用返回数据
        SignatureDto signatureDto = tradeLogApi.proxyBindCardLog(bankCardAddDTO);

        return signatureDto;
    }

    /**
     * 通过id查询用户信息
     * @param userId
     * @return
     */
    @Override
    public UserInfoVo findById(Long userId) {

        UserInfoVo userInfoVo = new UserInfoVo();
        // username\identityCode 都是在 u_user表中
        // userCode  u_detail 表中

        //查询数据 组装数据
        User user = getOne(Wrappers.lambdaQuery(User.class).eq(User::getId, userId)); //A
        UserDetail userDetail = userDetailServiceImpl.getOne(Wrappers.lambdaQuery(UserDetail.class).eq(UserDetail::getId, userId)); //B

        //A(很复杂的数据)\B（很复杂的数据） 查询完毕后 组装C（大数据）数据 这个场景常见
        //A\B 查询完毕后（可以同时进行） 组装C数据 这个场景JUC中的工具类
        // https://blog.csdn.net/hekai7217/article/details/145875359
        // 解决方案： CompletableFuture + 线程池
        //           CountDownLatch + 多线程解决

        //组装数据
        userInfoVo.setUserName(user.getUserName());
        userInfoVo.setIdentityCode(user.getIdentityCode());
        userInfoVo.setUserCode(userDetail.getUserCode());

        return userInfoVo;

    }

    /**
     * 账号登录
     * @param loginDto
     * @return
     */
    private LoginVo loginByAccount(LoginDto loginDto) {

        //01 查询用户
        User dbUser = getOne(Wrappers.lambdaQuery(User.class).eq(User::getUserName, loginDto.getLoginName()));
        //对象为null 抛出异常
        Assert.error(ObjectUtils.isEmpty(dbUser),BusinessCode.USER_LOGIN_ERROR);

//        Assert.error(dbUser == null,BusinessCode.USER_LOGIN_ERROR);
        //02 比较密码

        boolean checkPwd = dbUser.getPwd().equals(loginDto.getLoginSecret());
        //秘密不对 抛出异常
        Assert.error(!checkPwd,BusinessCode.USER_LOGIN_ERROR);

        //03 返回结果
        LoginVo loginVo = getLoginVo(dbUser);

        return loginVo;
    }

    /**
     * 通过user 构造一个 LoginVo
     * @param dbUser
     * @return
     */
    private LoginVo getLoginVo(User dbUser) {

        Map<String,Object> payLoad = new HashMap<>();
        payLoad.put("userId", dbUser.getId());
        payLoad.put("phone", dbUser.getPhone());
        String token = jwtTemplate.createToken(payLoad);

        String redisKey = "user:token:" + dbUser.getId();

        // 存储到Redis
        stringRedisTemplate.opsForValue().set(
                redisKey,
                token,
                Duration.ofMinutes(30));

        LoginVo loginVo = LoginVo.builder()
                .id(dbUser.getId())
                .phone(dbUser.getPhone())
                .token(token)
                .build();

        return loginVo;
    }

    /**
     * 验证码登录
     *    1、判断验证码
     *          抛出异常
     *    2、通过手机号码判断用户是否存在
     *
     *    3、存在 返回对象
     *         不存在 就进行注册
     *
     * @param loginDto
     * @return
     */
    private LoginVo loginByPhone(LoginDto loginDto) {

        //01 先获取验证码
        String phone = loginDto.getLoginName();
        String redisCode = stringRedisTemplate.opsForValue().get(phone);
        //判断 是否验证码过期

//        redisCode !=null && redisCode.length() == 0

        Assert.error(StringUtils.isEmpty(redisCode),BusinessCode.USER_VERIFY_CODE_EMPTY);
        //判断 是否验证是否正确
        Assert.error(!loginDto.getLoginSecret().equals(redisCode),BusinessCode.USER_VERIFY_CODE_ERROR);
        //02 通过手机号码判断用户是否存在
        LambdaQueryWrapper<User> userLambdaQueryWrapper = Wrappers.lambdaQuery(User.class).eq(User::getPhone, phone);
        User user = getOne(userLambdaQueryWrapper);
        
        if (ObjectUtils.isEmpty(user)) {
            //注册
            UserRegisterDto userRegisterDto = new UserRegisterDto();
            userRegisterDto.setUserName(loginDto.getLoginName());
            userRegisterDto.setPwd("123456");
            userRegisterDto.setPhone(loginDto.getLoginName());
            saveUser(userRegisterDto);

            //返回一个 注册的 对象
            User saveUser = getOne(userLambdaQueryWrapper);
            return getLoginVo(saveUser);
        }

        LoginVo loginVo = getLoginVo(user);
        return loginVo;
    }
}
